﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Collections;

namespace CIBI_MVVM.Helpers
{
    /// <summary>
    /// ObeservableLinkedList extends the standard LinkedList class by providing notifications for 
    /// Add,Remove and Clear methods
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObservableLinkedList<T> : LinkedList<T>,INotifyCollectionChanged
    {
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public ObservableLinkedList() : base() { }
        public ObservableLinkedList(IEnumerable<T> type) : base(type) { }

        /// <summary>
        /// Return the node at the specified index
        /// </summary>
        /// <param name="index">index</param>
        /// <returns>The node</returns>
        public T this[int index]
        {
            get
            {
                int i = 0;
                foreach (T n in this)
                {
                    if (i == index)
                    {
                        return n;
                    }
                }

                throw new ArgumentOutOfRangeException();
                
            }
        }


        /// <summary>
        /// Adds the new node right after the specified node. 
        /// </summary>
        /// <param name="node">The node which the new node will be added right after</param>
        /// <param name="newNode">The node to be added</param>
        public new void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode ) 
        {
            int i = IndexOf(node);
            base.AddAfter(node, newNode);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,newNode,i));
        }

        /// <summary>
        /// Adds a new node with the specified value right after the specified node. 
        /// </summary>
        /// <param name="node">The node which the new node will be added right after</param>
        /// <param name="value">The value of the new node to be added</param>
        public new void AddAfter(LinkedListNode<T> node, T value)
        {
            int i = IndexOf(value);
            base.AddAfter(node, value);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,value,i));
        }


        /// <summary>
        /// Adds the new node right before the specified node. 
        /// </summary>
        /// <param name="node">The node which the new node will be added right before</param>
        /// <param name="newNode">The node to be added</param>
        public new void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode) 
        {
            int i = IndexOf(node);
            base.AddBefore(node, newNode);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,newNode,i));
        }

        /// <summary>
        /// Adds a new node with the specified value right before the specified node. 
        /// </summary>
        /// <param name="node">The node which the new node will be added right before</param>
        /// <param name="value">The value of the new node to be added</param>
        public new void AddBefore(LinkedListNode<T> node, T value)
        {
            int i = IndexOf(value);
            base.AddBefore(node, value);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,value,i));
        }

        /// <summary>
        /// Adds the new node at the start of the ObservableLinkedList
        /// </summary>
        /// <param name="node">The node to be added</param>
        public new void AddFirst(LinkedListNode<T> node) 
        {
            base.AddFirst(node);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, 0));
        }

        /// <summary>
        ///  Adds a new node with the specified value at the start of the ObservableLinkedList
        /// </summary>
        /// <param name="value">The value of the new node</param>
        public new void AddFirst(T value)
        {
            base.AddFirst(value);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,value,0));
        }

        /// <summary>
        /// Adds the new node at the start of the ObservableLinkedList. Dummy node exists only for 
        /// allowing the method to be added to delegates.
        /// </summary>
        /// <param name="node">The node to be added<</param>
        /// <param name="dummyNode">Dummy node</param>
        public void AddFirst(LinkedListNode<T> node, LinkedListNode<T> dummyNode)
        {
            base.AddFirst(node);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,node,0));
        }

        /// <summary>
        /// Adds the new node at the end of the ObservableLinkedList
        /// </summary>
        /// <param name="node">The node to added</param>
        public new void AddLast(LinkedListNode<T> node)
        {
            base.AddLast(node);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,node,this.Count));
        }

        /// <summary>
        ///  Adds a new node with the specified value at the end of the ObservableLinkedList
        /// </summary>
        /// <param name="value">The value of the new node</param>
        public new void AddLast(T value)
        {
            base.AddLast(value);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value,this.Count));
        }

        /// <summary>
        /// Adds the new node at the end of the ObservableLinkedList. Dummy node exists only for 
        /// allowing the mehod to be added to delegates.
        /// </summary>
        /// <param name="node">The node to be added<</param>
        /// <param name="dummyNode">Dummy node</param>
        public void AddLast(LinkedListNode<T> node, LinkedListNode<T> dummyNode)
        {
            base.AddLast(node);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,node,this.Count));
        }

        /// <summary>
        /// Removes the specified node from the ObservableLinkedList.
        /// </summary>
        /// <param name="node">The node to be removed</param>
        public new void Remove(LinkedListNode<T> node)
        {
            base.Remove(node);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
        }

        /// <summary>
        /// Removes the firts occurence of node with the specified value from the ObservableLinkedList.
        /// </summary>
        /// <param name="value">The value of the noded</param>
        /// <returns>True if value exists, false otherwise</returns>
        public new bool Remove(T value)
        {
            var x = base.Remove(value);
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,value,0));
            return x;
        }
        
        /// <summary>
        /// Removes the last node from the ObservableLinkedList.
        /// </summary>
        public new void RemoveLast()
        {
            base.RemoveLast();
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
        }

        /// <summary>
        /// Removes the first node from the ObservableLinkedList.
        /// </summary>
        public new void RemoveFirst()
        {
            base.RemoveFirst();
            OnCollectionCanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
        }

        /// <summary>
        /// Finds the first occurance of the specified node
        /// </summary>
        /// <param name="node">Node to search for</param>
        /// <returns>Zero-based index of the node, -1 if not found</returns>
        public int IndexOf(LinkedListNode<T> node) 
        {
            int i = 0;
            foreach (var n in this) 
            {
                if (n.Equals(node)) 
                {
                    return i;
                }
                i++;
            }

            return -1;
        }

        /// <summary>
        /// Finds the first occurance of the specified value
        /// </summary>
        /// <param name="value">Value to search for</param>
        /// <returns>Zero-based index of the node, -1 if not found</returns>
        public int IndexOf(T value)
        {
            return IndexOf(new LinkedListNode<T>(value));
        }

        /// <summary>
        /// Exist only for usage by delegates
        /// </summary>
        /// <param name="node"></param>
        public void DoNothing(T node)
        {
        }

        /// <summary>
        /// Handles the CollectionChanged event
        /// </summary>
        /// <param name="e"></param>
        private void OnCollectionCanged(NotifyCollectionChangedEventArgs e) 
        {
            if (CollectionChanged != null) 
            {
                CollectionChanged(this, e);            
            }
        }
    }
}
